{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<h1 style=\"text-align: left;\">Introduction:</h1>\n",
    "<a href=\"https://en.wikipedia.org/wiki/Machine_learning\" target=\"_blank\">Machine Learning</a> is a vast area of Computer Science that is concerned with designing algorithms which form good models of the world around us (the data coming from the world around us).\n",
    "\n",
    "Within Machine Learning many tasks are - or can be reformulated as - classification tasks. \n",
    "\n",
    "In classification tasks we are trying to produce a model which can give the correlation between the input data $X$ and the class $C$ each input belongs to. This model is formed with the feature-values of the input-data. For example, the dataset contains datapoints belonging to the classes *Apples*, *Pears* and *Oranges* and based on the features of the datapoints (weight, color, size etc) we are trying to predict the class. \n",
    "\n",
    "We need some amount of training data to train the Classifier, i.e. form a correct model of the data. We can then use the trained Classifier to classify new data. If the training dataset chosen correctly, the Classifier should predict the class probabilities of the new data with a similar accuracy (as it does for the training examples).\n",
    "\n",
    "![title](img/classification.PNG)\n",
    "\n",
    "After construction, such a Classifier could for example tell us that document containing the words \"Bose-Einstein condensate\" should be categorized as a Physics article, while documents containing the words \"Arbitrage\" and \"Hedging\" should be categorized as a Finance article.\n",
    "\n",
    "Another Classifier (whose dataset is illustrated below) could tell whether or not a person makes <a href=\"https://archive.ics.uci.edu/ml/datasets/Adult\" target=\"_blank\">more than 50K</a>, based on features such as Age, Education, Marital Status, Occupation etc. \n",
    "\n",
    "![title](img/classification_dataset3.PNG)\n",
    "\n",
    "As we can see, there is a input dataset $ X $ which corresponds to a 'output' $Y$. The dataset $X$ contains $m$ input examples  $x^{(1)}, x^{(2)}, .. , x^{(m)}$, and each input example has $n$ feature values $x_1, x_2, ..., x_n$ (here $n\\ =\\ 7$).  \n",
    "\n",
    "There are three popular Classifiers within Machine Learning, which use three different mathematical approaches to classify data; \n",
    "- **Naive Bayes**, which uses a statistical (Bayesian) approach, \n",
    "- **Logistic Regression**, which uses a functional approach and \n",
    "- **Support Vector Machines**, which uses a geometrical approach. \n",
    "\n",
    "Previously we have already looked at <a href=\"http://ataspinar.com/2016/05/07/regression-logistic-regression-and-maximum-entropy-part-2-code-examples/\" target=\"_blank\">Logistic Regression</a>. Here we will see the theory behind the Naive Bayes Classifier together with its implementation in Python. \n",
    "\n",
    "<h1 style=\"text-align: left;\"><a name=\"ch2\"></a>2. Naive Bayes Classification:</h1>\n",
    "Naive Bayes classifiers are trying to classify data from a Statistical point of view.\n",
    "\n",
    "The starting point is that the probability (datapoint $x^{i}$ belongs to a) class $C\\ =\\ c_i$ is given by the <a href=\"https://en.wikipedia.org/wiki/Posterior_probability\">posterior probability</a> $P(C\\ |\\ x^{i})$. Here $x^{i}$ refers to an entry in the test set, consisting of n features; $x_1, x_2, ..., x_n$.\n",
    "\n",
    "Using Bayes' rule, this posterior probability can be rewritten as:\n",
    "\n",
    "$ P(C=c_i\\ |\\ x^{i}) = \\frac{P(x^{i}\\ |\\ C=c_j) \\cdot P(C=c_j)}{P(x^{i})} $\n",
    "\n",
    "<br>\n",
    "Since the marginal probability $P(x^{i})$ does not depends on the classes, it can be disregarded and the equation becomes:\n",
    "\n",
    "$ P(C=c_j\\ |\\ x^{i}) = P(x^{i}\\ |\\ C=c_j) \\cdot P(C=c_j) $\n",
    "\n",
    "<br>\n",
    "The training example $x^{(i)} $ belongs to the class $c_j$ which maximizes this probability, so:\n",
    "\n",
    "$ C_{NB} = argmax\\ P(x^{(i)}|C=c_j) \\cdot P(C=c_j) $\n",
    "\n",
    "$ C_{NB} = argmax\\ P(x_1, x_2, .., x_n | C=c_j) \\cdot P(C=c_j) $\n",
    "\n",
    "<br>\n",
    "Assuming <a href=\"https://en.wikipedia.org/wiki/Conditional_independence\">conditional independence</a> of the features $ x_k$, this equation simplifies to:\n",
    "\n",
    "$ C_{NB} = argmax\\ P(x_1|C) \\cdot P(x_2|C) \\cdot \\cdot\\ \\cdot P(x_n|C) \\cdot P(C) $\n",
    "\n",
    "$ C_{NB} = argmax\\ P(C) \\cdot \\prod_i P(x_i|C) $\n",
    "\n",
    "<br>\n",
    "Here $P(x_i | C)$ is the conditional probability that feature i belongs to class $C$. \n",
    "\n",
    "This probability can simply be calculated by calculating the relative values of feature $i$ per class.\n",
    "This is should become more clear, if we look at our '50K income' example of above:\n",
    "\n",
    "<br>\n",
    "**First, we select all of the entries belonging to one class:**\n",
    "![title](img/classification_dataset3b.PNG)\n",
    "\n",
    "<br>\n",
    "![title](img/classification_dataset3c.png)\n",
    "\n",
    "<br>\n",
    "**Then we calculate the relative frequency of the values of each feature (per class):**\n",
    "![title](img/classification_dataset3d.png)\n",
    "\n",
    "\n",
    "![title](img/classification_dataset3e.png)\n",
    "\n",
    "\n",
    "**New entries can be classified by multiplying the probabilities of each feature per class:**<br>\n",
    "if a new entry has for the three features illustrated above, the following values: <br>\n",
    "+ native-country: United-states, \n",
    "+ hours-per-week: 40, \n",
    "+ occupation: Exec-managerial.\n",
    "\n",
    "Then based on these features, the class probabilties will be:<br>\n",
    "$P( C = C_{>50K})\\ =\\ (1/3) \\cdot (2/3) \\cdot (1/3) = (2/27) $<br>\n",
    "$P( C = C_{<=50K})\\ =\\ (2/3) \\cdot (2/3) \\cdot (2/3) = (8/27) $<br>\n",
    "\n",
    "The predicted class for this new entry therefore would be '<=50K'. \n",
    "\n",
    "In practice we of course have much more features, and thousands/millions of training examples, but the way Naive Bayes classification works remains the same. \n",
    "\n",
    "So we need to make a Hash table, containing the feature probabilities. \n",
    "\n",
    "Once such a Hash table is made, new entries can be classified by multiplying the probabilities of each feature value, per class. \n",
    "\n",
    "\n",
    "The code to train a Naive Bayes Classifier looks as follows. \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from collections import Counter, defaultdict\n",
    "import numpy as np\n",
    "\n",
    "class NaiveBaseClass:\n",
    "    def calculate_relative_occurences(self, list1):\n",
    "        no_examples = len(list1)\n",
    "        ro_dict = dict(Counter(list1))\n",
    "        for key in ro_dict.keys():\n",
    "            ro_dict[key] = ro_dict[key] / float(no_examples)\n",
    "        return ro_dict\n",
    "\n",
    "    def get_max_value_key(self, d1):\n",
    "        values = d1.values()\n",
    "        keys = d1.keys()\n",
    "        max_value_index = values.index(max(values))\n",
    "        max_key = keys[max_value_index]\n",
    "        return max_key\n",
    "       \n",
    "    def initialize_nb_dict(self):\n",
    "        self.nb_dict = {}\n",
    "        for label in self.labels:\n",
    "            self.nb_dict[label] = defaultdict(list)\n",
    "\n",
    "\n",
    "class NaiveBayes(NaiveBaseClass):\n",
    "    \"\"\"\n",
    "    Naive Bayes Classifier method:\n",
    "    It is trained with a 2D-array X (dimensions m,n) and a 1D array Y (dimension 1,n).\n",
    "    X should have one column per feature (total n) and one row per training example (total m).\n",
    "    After training a hash table is filled with the class probabilities per feature.\n",
    "    We start with an empty hash table nb_dict, which has the form:\n",
    "\n",
    "    nb_dict = {\n",
    "        'class1': {\n",
    "            'feature1': [],\n",
    "            'feature2': [],\n",
    "            (...)\n",
    "            'featuren': []\n",
    "        }\n",
    "        'class2': {\n",
    "            'feature1': [],\n",
    "            'feature2': [],\n",
    "            (...)\n",
    "            'featuren': []\n",
    "        }\n",
    "    }\n",
    "    \"\"\"\n",
    "    \n",
    "    def train(self, X, Y):\n",
    "        self.labels = np.unique(Y)\n",
    "        no_rows, no_cols = np.shape(X)\n",
    "        self.initialize_nb_dict(labels)\n",
    "        self.class_probabilities = self.calculate_relative_occurences(Y)\n",
    "        #iterate over all classes\n",
    "        for label in self.labels:\n",
    "            #first we get a list of indices per class, so we can take a subset X_ of the matrix X, containing data of only that class.\n",
    "            row_indices = np.where(Y == label)[0]\n",
    "            X_ = X[row_indices, :]\n",
    "\n",
    "            #in this subset, we iterate over all the columns/features, and add all values of each feature to the hash table nb_dict\n",
    "            no_rows_, no_cols_ = np.shape(X_)\n",
    "            for jj in range(0,no_cols_):\n",
    "                nb_dict[label][jj] += list(X_[:,jj])\n",
    "\n",
    "        #Now we have a Hash table containing all occurences of feature values, per feature, per class\n",
    "        #We need to transform this Hash table to a Hash table with relative feature value occurences per class\n",
    "        for label in self.labels:\n",
    "            for jj in range(0,no_cols):\n",
    "                self.nb_dict[label][jj] = self.calculate_relative_occurences(nb_dict[label][jj])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "Once the Naive Bayes Classifier has been trained with the train() method, we can use it to classify new elements:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "    def classify_single_elem(self, X_elem):\n",
    "        Y_dict = {}\n",
    "        #First we determine the class-probability of each class, and then we determine the class with the highest probability\n",
    "        for label in self.labels:\n",
    "            class_probability = self.class_probabilities[label]\n",
    "            for ii in range(0,len(X_elem)):\n",
    "              relative_feature_values = self.nb_dict[label][ii]\n",
    "              if X_elem[ii] in relative_feature_values.keys():\n",
    "                class_probability *= relative_feature_values[X_elem[ii]]\n",
    "              else:\n",
    "                class_probability *= 0\n",
    "            Y_dict[label] = class_probability\n",
    "        return self.get_max_value_key(Y_dict)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "For the rest of the Python code, including batch classification, and the Naive Bayes classification of text-data and and worked out examples of both Classifiers, please have a look at the <a href=\"https://github.com/taspinar/siml/blob/master/siml/naive_bayes.py\" target=\"_blank\">GitHub repository</a>. \n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python [conda root]",
   "language": "python",
   "name": "conda-root-py"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
